/**
 * 							PHYSICAL MEMORY MANAGEMENT
 * @brief	physical memory management
 * @author	chy.
 * @note	this file only management the physical memory page!
 * @comment	ARM MMU support many modes.But we use 4KB per page.
 */

#ifndef	_K_MEM_PHYSICAL_C_
#define	_K_MEM_PHYSICAL_C_

#include "k_mem_physical.h"
#include "../k_common_asm.h"

/**
 * 								GLOBALS
 */
static	char	g_s_k_mem_phy_pagemgrtable[K_VAL_MAXMEMSIZE<<8];	// physical page manager
unsigned int 	g_s_k_mem_phy_pagestart;							// physical page start index
unsigned int 	g_s_k_mem_phy_pageend;								// physical page end index

unsigned int	g_s_k_mem_phy_sys_start;							// OS's physical page start index
unsigned int	g_s_k_mem_phy_sys_end;								// OS's physical page end index

/**
 * 								FUNCTIONS
 */

/**
 * @BRIEF	:	Initialize Physical Memory
 * @PARAM	:	ram_phy_startaddr	physical starting address of RAM in system
 * @PARAM	:	ram_phy_endaddr		physical end address of RAM in system
 * @RETURN	:	void
 */
void k_mem_phy_init(unsigned int ram_phy_startaddr, unsigned int ram_phy_endaddr)
{
	int	i = 0;

	// reside the start address and end address at 4KB boud
	ram_phy_startaddr = (ram_phy_startaddr + ((1<<12)-1))&(~((1<<12)-1));
	ram_phy_endaddr = (ram_phy_endaddr + ((1<<12)-1))&(~((1<<12)-1));
	
	g_s_k_mem_phy_pagestart	= ram_phy_startaddr >> 12;
	g_s_k_mem_phy_pageend	= ram_phy_endaddr >> 12;

	/** we need to indicate the pages which fixed being used for OS */
	/** the count of pages defined in file basedefine.h **/
	for(i = 0; i < K_SYS_SIZE; i++)
	{
		K_MEM_PHY_SETSYSMODE(i);
	}

	/** we indicate which pages will be mapped to OS virtual space **/
	g_s_k_mem_phy_sys_start = g_s_k_mem_phy_pagestart + K_SYS_SIZE;	 //	the fixed physical pages will never be freed!So, them are exclude!
																	 // then, we can find page faster!

	g_s_k_mem_phy_sys_end	= (g_s_k_mem_phy_pagestart + (K_SYS_MEM_VIR_SIZE << 8)) > g_s_k_mem_phy_pageend ? g_s_k_mem_phy_pageend:(g_s_k_mem_phy_pagestart + (K_SYS_MEM_VIR_SIZE << 8));

}

/**
 * @BRIEF	:	Allocate a Physical Memory Page
 * @PARAM	:	mem_mode			indicate whether need to allocate in OS space
 * @PARAM	:	page_count			count of physical pages need to allocate in continuous
 * @RETURN	:	the physical address of page start
 * @NOTE	:	this can allocate continuous physical pages
 * 			:	this function thinks interrupt is disabled!
 * 			:	So,need to control interrupt when using this.
 * 			:	$$ In fact, we don't need to allocate continuous physical pages usually
 */
void* k_mem_phy_allocate(EN_K_MEM_PHY_USE_MODE mem_mode, unsigned int page_count)
{
	unsigned int idx = 0;
	unsigned int cnt_tmp = 0;
	unsigned int ntmp;

	if(page_count == 0)
	{
		return 0;
	}

	if(mem_mode == K_MEM_PHY_SYSTEM)
	{
		/* we allocate system mode physical pages in OS physical space */
		for(idx = g_s_k_mem_phy_sys_start; idx < g_s_k_mem_phy_sys_end; idx++)
		{
			if(!K_MEM_PHY_PAGEUSING(idx))
			{
				cnt_tmp++;
			}
			else
			{
				cnt_tmp=0;
				continue;
			}

			if(cnt_tmp == page_count)
			{
				// we got it ^_^
				unsigned int hit_idx = idx - cnt_tmp + 1;
				for(ntmp = hit_idx; ntmp <= idx; ntmp++)
				{
					// mark them
					K_MEM_PHY_SETSYSMODE(ntmp - g_s_k_mem_phy_pagestart);
				}

				// return the physical address of first page
				return (void *)(K_MEM_PHY_PMTIDX2PHYMMADDR(hit_idx));
			}
		}

		/*
			we can't find a free physical page... :(
			we need to swap some USER MODE physical pages *in OS physical space* to disk! And re-find
			MAKE SURE:
				SWAP PAGES which are in OS PHYSICAL SPACE, because we need to allocate pages in
				OS physical space!
		*/

		//-------------------------------------------------------
	}
	else
	{
		/*
		 * @@@@ in fact, we don't need to allocate continuous physical pages usually @@@@
		 */
		/**
		 * we allocate user mode physical page should from the end of physical space
		 * in order to avoid waist physical page in OS physical space
		 **/
		for(idx = g_s_k_mem_phy_pageend - 1; idx >= g_s_k_mem_phy_pagestart; idx--)
		{
			if(!K_MEM_PHY_PAGEUSING(idx))
			{
				cnt_tmp++;
			}
			else
			{
				cnt_tmp=0;
				continue;
			}

			if(cnt_tmp == page_count)
			{
				// we got it ^_^
				unsigned int hit_idx_end = idx + cnt_tmp;		// it's different from SYSTME MODE, as it's DES when scanning!
				for(ntmp = idx; ntmp < hit_idx_end; ntmp++)
				{
					// mark them
					K_MEM_PHY_SETUSERMODE(ntmp - g_s_k_mem_phy_pagestart);
				}

				// return the physical address of first page
				return (void *)(K_MEM_PHY_PMTIDX2PHYMMADDR(idx));
			}
		}

		/** we can't find free physical page(s), we need to swap some USER MODE physical pages to disk, and re-find !*/

		/*-------------------- */
	}

	return 0;	// 0 physical address are always used, so we can use this to indicate that we fail!
}

/**
 * @BRIEF	:	Free a Physical Memory Page
 * @PARAM	:	phy_addr			physical address of page to free
 * @RETURN	:	void
 * @NOTE	:	=>this will free just one physical page!
 */
void k_mem_phy_free(unsigned int phy_addr)
{
	unsigned int phy_page_idx = K_MEM_PHY_PHYMMADDR2PMTIDX(phy_addr);
	unsigned int phy_page_mgr_idx = phy_page_idx - g_s_k_mem_phy_pagestart;
	if(phy_page_idx < g_s_k_mem_phy_sys_start)
	{
		// there must be some error!!!, can't free OS program memroy!
		return;
	}
	K_MEM_PHY_SETFREE(phy_page_mgr_idx);
}

/**
 * @BRIEF	:	Allocate First Level Page Table
 * @PARAM	:	void
 * @RETURN	:	physical address of First Level Page Table
 * @NOTE	:	this function will allocate 4 continuous physical pages
 * 			:	also, it' started at 16KB bound, as system needed.
 * @WARNNING: =>This function thinks that interrupt has been disabled
 * 			:	So, make sure interrupt has been disabled when calling this!
 */
void* k_mem_phy_allocate_flpt()
{
	/** we should do this in OS physical space */
	unsigned int idx;
	for(idx = g_s_k_mem_phy_sys_start; idx < g_s_k_mem_phy_sys_end; idx+=4)
	{
		/**
		 * As we defined in file basedefine.h, we can make sure that,
		 * g_s_k_mem_phy_sys_start is bound at 16KB.
		 */
		if(K_MEM_PHY_PAGEUSING(idx))
		{
			continue;
		}

		if(K_MEM_PHY_PAGEUSING(idx + 3))
		{
			continue;
		}

		if(!K_MEM_PHY_PAGEUSING(idx + 1) && !K_MEM_PHY_PAGEUSING(idx + 2))
		{
			// we got it ^_^
			int i;
			// make them
			for(i = 0; i <4; i++)
			{
				K_MEM_PHY_SETSYSMODE(idx + i - g_s_k_mem_phy_pagestart);
			}

			// return
			return	(void *)(K_MEM_PHY_PMTIDX2PHYMMADDR(idx));
		}
	}

	/*
		We can't find  free physical pages in OS physical space ... :(
		We need to swap some USER MODE physical pages *in OS physical space* to disk! And re-find
		MAKE SURE:
			SWAP PAGES which are in OS PHYSICAL SPACE, because we need to allocate pages in
			OS physical space!
	*/
	return 0;	// 0 physical address are always used, so we can use this to indicate that we fail!
}

#endif	//	_K_MEM_PHYSICAL_C_
